﻿using System;
using System.Diagnostics;
using System.Linq.Expressions;
using System.Reflection;

namespace ExpressionMapper
{
    using MappingExpressionGenerator = Func<Expression, Type, Expression, ParameterExpression, Expression>;

    public static partial class Generators
    {
        /// <summary>
        /// Returns generator that uses Convert class for mapping.
        /// </summary>
        public static MappingExpressionGenerator UseConvertClass()
        {
            return
                (fromExpr, to, customMapping, instanceDictionary) => (SupportedByConvertClass(fromExpr.Type) && SupportedByConvertClass(to))
                                                     ? customMapping ?? GenrateConvertClassExpression(fromExpr, to)
                                                     : null;
        }

        private static Expression GenrateConvertClassExpression(Expression fromExpr, Type to)
        {
            Debug.WriteLine(string.Format("UseConvertClass ({0}): {1} to {2}", fromExpr.NodeType, fromExpr.Type.Name,
                                  to.Name));

            if (IsNullablePrimitive(fromExpr.Type))
                fromExpr = Expression.Convert(fromExpr, typeof(object));
           
            Expression convertxpression = Expression.Call(GetConvertClassMethod(fromExpr.Type, to), fromExpr);

            if (IsNullablePrimitive(to))
                convertxpression = Expression.Convert(convertxpression, to);
            
            return convertxpression;
        }

        private static MethodInfo GetConvertClassMethod(Type from, Type to)
        {
            string toTypeName = to.Name;
            if (IsNullablePrimitive(to))
                toTypeName = to.GetGenericArguments()[0].Name;

            Type contype = typeof (Convert);
            return contype.GetMethod("To" + toTypeName, BindingFlags.Static | BindingFlags.Public,
                                     null, new[] {from}, new ParameterModifier[0]);
        }

        private static bool SupportedByConvertClass(Type t)
        {
            return (IsPrimitive(t) && !IsNullable(t)) || IsNullablePrimitive(t);// (t.IsPrimitive && !t.IsPointer) // IsPointer ?
        }

        private static bool IsNullablePrimitive(Type t)
        {
            return IsNullable(t) && IsPrimitive(t.GetGenericArguments()[0]);
        }
    }
}